// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2023. All rights reserved.
 * Description: callback function for memory hardware error caused panic
 * Author: Lv Ying
 * Create: 2019-06-19
 */

#include <linux/mm.h>
#include <linux/notifier.h>
#include <linux/ras_mem_offline.h>

static BLOCKING_NOTIFIER_HEAD(fma_memory_offline_chain);

static const char *fma_mem_offline_notify_type_names[FMA_MEM_OFFLINE_NOTIFY_COUNT] = {
	"FMA_MEM_OFFLINE_NOTIFY_PAGE",
	"FMA_MEM_OFFLINE_NOTIFY_INJECT",
	"FMA_MEM_OFFLINE_NOTIFY_SRAO",
	"FMA_MEM_OFFLINE_NOTIFY_SRAR"
};

int fma_register_memory_offline_notifier(struct notifier_block *nb)
{
	return blocking_notifier_chain_register(&fma_memory_offline_chain, nb);
}
EXPORT_SYMBOL(fma_register_memory_offline_notifier);

void fma_unregister_memory_offline_notifier(struct notifier_block *nb)
{
	blocking_notifier_chain_unregister(&fma_memory_offline_chain, nb);
}
EXPORT_SYMBOL(fma_unregister_memory_offline_notifier);

int fma_memory_offline_notify(unsigned long event, void *v)
{
	int res;

	if (event >= FMA_MEM_OFFLINE_NOTIFY_COUNT) {
		pr_warn("RAS: wrong event(%lu) in %s", event, __func__);
		return -EINVAL;
	}
	pr_warn("RAS: In %s, ready to call notify chain to isolate page: 0x%lx, notify type: %s\n",
			__func__, (*(unsigned long *)v), fma_mem_offline_notify_type_names[event]);
	res = blocking_notifier_call_chain(&fma_memory_offline_chain, event, v);
	return res;
}

int recover_original_flag(int flags)
{
	return flags & ((1 << MF_NOTIFY_BASE) - 1);
}

int fma_memory_offline_type(int flags)
{
	if (flags & MF_PAGE)
		return FMA_MEM_OFFLINE_NOTIFY_PAGE;
	else if (flags & MF_INJECT)
		return FMA_MEM_OFFLINE_NOTIFY_INJECT;
	else if (flags & MF_SRAO)
		return FMA_MEM_OFFLINE_NOTIFY_SRAO;
	else if (flags & MF_SRAR)
		return FMA_MEM_OFFLINE_NOTIFY_SRAR;
	else
		return FMA_MEM_OFFLINE_NOTIFY_PAGE;
}

#ifdef CONFIG_EULEROS_RAS_MEM_OFFLINE_NOTIFY
int fma_notifier_to_errno(int *flags, unsigned long pfn)
{
	int res;

	res = fma_memory_offline_notify(fma_memory_offline_type(*flags), &pfn);
	if ((res != -EINVAL) &&  ((unsigned int)res & NOTIFY_STOP_MASK))
		return notifier_to_errno(res); // notifier_to_errno() value <= 0
	*flags = recover_original_flag(*flags);
	return NO_NOTIFIER_TO_ERROR;
}
#endif
